// (c) 2015 Teresa Holfeld
package com.holfeld.teresa.vocabtrainer.util;

import android.content.Context;
import android.content.res.AssetManager;

import com.holfeld.teresa.vocabtrainer.provider.VocablePair;
import com.holfeld.teresa.vocabtrainer.provider.VocablePairFactory;
import com.holfeld.teresa.vocabtrainer.provider.Vocabulary;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper class for file input/output.
 * 
 * @author Teresa Holfeld, teresa.holfeld@googlemail.com
 *
 */
public class FileIO {
	
	private final static String TAB = "\t";
	private final static String BREAK = "\r\n";
	private final static String ENCODING = "UTF-8";

	/**
	 * Saves given Vocabulary to a file named after vocabulary in the device's internal storage.
	 * 
	 * @param context the Android context
	 * @param vocabulary the vocabulary to save
	 */
	public static void saveToStorage(Context context, Vocabulary vocabulary) {
		String name = escapeFileSeparator(vocabulary.getName());
		String locale1 = vocabulary.getLocale1();
		String locale2 = vocabulary.getLocale2();
		FileOutputStream out;
		try {
			out = context.openFileOutput(name, Context.MODE_PRIVATE);
            out.write((encodeBase64(name) + BREAK).getBytes());
			out.write((encodeBase64(locale1) + TAB).getBytes());
			out.write((encodeBase64(locale2) + BREAK).getBytes());
			for (int i = 0; i < vocabulary.size(); i++) {
				VocablePair vocablePair = vocabulary.get(i);
				String vocable1 = vocablePair.getVocable(locale1);
				String vocable2 = vocablePair.getVocable(locale2);
				out.write((encodeBase64(vocable1) + TAB + encodeBase64(vocable2) + BREAK)
                        .getBytes());
			}
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Reads and parses file with given name in the device's internal storage and builds a 
	 * Vocabulary representation.
	 * 
	 * @param context the Android context
	 * @param name the file name of the vocabulary to load
	 * @return the loaded vocabulary
	 */
	public static Vocabulary readFromStorage(Context context, String name) {
		Vocabulary vocabulary = null;
		FileInputStream inStream;
		try {
			inStream = context.openFileInput(escapeFileSeparator(name));
			vocabulary = parseStreamToVocabulary(inStream, true);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return vocabulary;
	}
	
	/**
	 * Checks if a file with given filename is saved by the application.
	 * 
	 * @param context the Android context
	 * @param filename the file name to check for existence
	 * @return true if the file exists, false otherwise
	 */
	public static boolean exists(Context context, String filename) {
		String[] files = context.fileList();
		for (String file : files) {
			if (file.equals(filename)) return true;
		}
		return false;
	}

	/**
	 * Gets all vocabularies saved by the application. 
	 * 
	 * @param context the Android context
	 * @return the list of vocabulary files for the given context
	 */
	public static List<Vocabulary> getVocabularies(Context context) {
		String[] files = context.fileList();
		List<Vocabulary> vocabularies = new ArrayList<Vocabulary>();
		for (String file : files) {
			Vocabulary vocabulary = readFromStorage(context, file);
			vocabularies.add(vocabulary);
		}
		return vocabularies;
	}
	
	/**
	 * Deletes file with name in internal storage.
	 * 
	 * @param context the Android context
	 * @param name the file name of the vocabulary to delete
	 * @return true if the deletion was successful
	 */
	public static boolean deleteVocabulary(Context context, String name) {
		return context.deleteFile(name);
	}

    /**
     * Gets the example vocabularies that come with the app.
     *
     * @param context the Android context
     * @return the example vocabularies
     */
	public static List<Vocabulary> getBuiltInVocabularies(Context context) {
		List<Vocabulary> vocabularies = new ArrayList<Vocabulary>();
		AssetManager am = context.getResources().getAssets();
		String[] assets;
        try {
            assets = am.list("");
            for (String asset : assets) {
            	if (!isBuiltInVocabulary(asset)) {
            		continue;
            	}
            	InputStream inStream = am.open(asset);
            	Vocabulary vocabulary = parseStreamToVocabulary(inStream, false);
    			vocabularies.add(vocabulary);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		
		return vocabularies;
	}

    /**
     * Checks if there are built-in example vocabularies.
     *
     * @param context the Android context
     * @return true if there exist built-in vocabularies
     */
    public static boolean builtInVocabulariesExist(Context context) {
        AssetManager am = context.getResources().getAssets();
        String[] assets;
        try {
            assets = am.list("");
            for (String asset : assets) {
                if (isBuiltInVocabulary(asset) && !FileIO.exists(context, asset)) return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * TODO: this check probably has to be done differently.
     * Checks if the given vocabulary file name is a built-in example vocabulary.
     *
     * @param name the file name of the vocabulary to check
     * @return true if it is a built-in vocabulary
     */
	private static boolean isBuiltInVocabulary(String name) {
		return (!name.equals("sounds") && !name.equals("images") && !name.equals("webkit"));
	}

    /**
     * Parses a given InputStream to a Vocabulary representation.
     *
     * @param inStream the input stream of the vocabulary file
     * @param decode whether the vocabulary has to be decoded from Base64
     * @return the vocabulary representation
     */
	private static Vocabulary parseStreamToVocabulary(InputStream inStream, boolean decode) {
		BufferedReader reader;
        String vocabularyName = "Vocabulary";
    	VocablePairFactory factory = null;
    	Vocabulary vocabulary = null;
		int i = 0;
		String line;
		try {
			reader = new BufferedReader(new InputStreamReader(inStream, ENCODING));
			while ((line = reader.readLine()) != null) {
		    	if (i == 0) {
                    if (decode) {
                        vocabularyName = decodeBase64(line);
                    } else {
                        vocabularyName = line;
                    }
                }
                else if (i == 1) {
		    		String[] locales = line.split(TAB);
		    		String locale1, locale2;
		    		if (decode) {
		    			locale1 = decodeBase64(locales[0]);
		    			locale2 = decodeBase64(locales[1]);
		    		} else {
		    			locale1 = locales[0];
		    			locale2 = locales[1];
		    		}
		    		vocabulary =
                            new Vocabulary(unescapeFileSeparator(vocabularyName), locale1, locale2);
		    		factory = new VocablePairFactory(locale1, locale2);
		    	} else {
		    		String[] vocables = line.split(TAB);
		    		String vocable1, vocable2;
		    		if (decode) {
		    			vocable1 = decodeBase64(vocables[0]);
		    			vocable2 = decodeBase64(vocables[1]);
		    		} else {
		    			vocable1 = vocables[0];
		    			vocable2 = vocables[1];
		    		}
		    		VocablePair vocablePair = factory.createVocablePair(vocable1, vocable2);
		    		vocabulary.put(vocablePair);
		    	}
		    	++i;		    	
		    }                  
			inStream.close();
		} catch (IOException | NullPointerException e) {
            e.printStackTrace();
        }
		return vocabulary;
	}

    /**
     * Escapes all file separator characters ("/").
     *
     * @param unescaped the string to escape
     * @return the escaped string
     */
	private static String escapeFileSeparator(String unescaped) {
		return unescaped.replaceAll("/", "%20");
	}

    /**
     * Unescapes all file separator characters ("/").
     *
     * @param escaped the string to unescape
     * @return the unescaped string
     */
	private static String unescapeFileSeparator(String escaped) {
		return escaped.replaceAll("%20", "/");
	}

    /**
     * Encodes the given string as Base 64.
     *
     * @see Base64
     * @param input the string to encode
     * @return the encoded string
     */
	private static String encodeBase64(String input) {
		byte[] inputBytes = (input).getBytes();
		return Base64.encodeBytes(inputBytes);
	}

    /**
     * Decodes the given Base 64 string.
     *
     * @see Base64
     * @param input the Base 64 encoded string to decode
     * @return the decoded string
     */
	private static String decodeBase64(String input) {
		String output = null;
		try {
			byte[] bytes = Base64.decode(input);
			output = new String(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return output;
	}
}
